package com.chiangleoi.appointment.utils;

import org.springframework.beans.BeanUtils;
import org.springframework.cglib.beans.BeanCopier;

import java.lang.ref.Reference;
import java.lang.ref.SoftReference;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 实例拷贝工具
 *
 * @author JiangLei7
 */
public class BeanCopyUtil {

    private static BeanCopierManager beanCopierManager = new BeanCopierManager();

    public static <T> T copy(Object source, T target) {
        try {
            BeanCopier beanCopier = beanCopierManager.getBeanCopier(source.getClass(), target.getClass());
            beanCopier.copy(source, target, null);
        } catch (Exception e) {
            BeanUtils.copyProperties(source, target);
        }
        return target;
    }

    public static <T> T copy(T source) {
        if (source == null) {
            return null;
        }
        return copy(source, (Class<T>) source.getClass());
    }

    @SuppressWarnings("unchecked")
    public static <T> T copy(Object source, Class<T> targetClass) {
        if (source == null) {
            return null;
        }
        T target;
        try {
            Constructor<T> noArgsConstructor = targetClass.getDeclaredConstructor();
            if (!noArgsConstructor.isAccessible()) {
                noArgsConstructor.setAccessible(true);
            }
            target = noArgsConstructor.newInstance();
        } catch (Exception e) {
            throw new ConstructTargetClassException(targetClass, e);
        }
        return copy(source, target);
    }

    public static <T> List<T> copy(List<?> sources, Class<T> targetClass) {
        if (sources == null) {
            return null;
        }
        List<T> result = new ArrayList<T>();
        for (Object obj : sources) {
            result.add(copy(obj, targetClass));
        }
        return result;
    }

    /**
     * Bean复制管理类
     * @author JiangLei7
     */
    private static class BeanCopierManager {

        private Map<String, Reference<BeanCopier>> beanCopierMap = new ConcurrentHashMap<String, Reference<BeanCopier>>(16);

        private BeanCopier getBeanCopier(Class<?> source, Class<?> target) {
            String cacheKey = generateCacheKey(source, target);
            BeanCopier beanCopier = getBeanCopier(cacheKey);
            if (beanCopier == null) {
                beanCopier = BeanCopier.create(source, target, false);
                saveCache(cacheKey, beanCopier);
            }
            return beanCopier;
        }

        private String generateCacheKey(Class<?> source, Class<?> target) {
            return source.getName() + "-" + target.getName();
        }

        private void saveCache(String cacheKey, BeanCopier beanCopier) {
            Reference<BeanCopier> beanCopierSoftReference = new SoftReference<BeanCopier>(beanCopier);
            beanCopierMap.put(cacheKey, beanCopierSoftReference);
        }

        private BeanCopier getBeanCopier(String cacheKey) {
            Reference<BeanCopier> beanCopierSoftReference = beanCopierMap.get(cacheKey);
            return beanCopierSoftReference == null ? null : beanCopierSoftReference.get();
        }
    }

    /**
     * 构造方法实例化失败异常
     * @author JiangLei7
     */
    public static class ConstructTargetClassException extends RuntimeException {

        public ConstructTargetClassException(Class<?> clazz, Exception e) {
            super(clazz.getName() + "实例化失败：", e);
        }

    }

}